package com.woniuxy.portal.controller;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.woniuxy.common.RedisConst;
import com.woniuxy.portal.common.enumeration.Assert;
import com.woniuxy.portal.common.enumeration.BusinessCode;
import com.woniuxy.portal.common.enumeration.UserStatus;
import com.woniuxy.portal.model.dto.LoginRequest;
import com.woniuxy.portal.model.dto.RegisterRequest;
import com.woniuxy.portal.model.vo.UserVo;
import com.woniuxy.portal.service.UserService;
import com.woniuxy.portal.model.entity.User;
import com.woniuxy.portal.utils.EmailUtils;
import com.woniuxy.portal.utils.JwtUtil;
import com.woniuxy.portal.utils.SmsUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author yellow Docter
 * @date 2024-10-18
 * @desc 蜗牛创想：www.woniuxy.com
 */
@Slf4j
@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private SmsUtil smsUtil;

    @Autowired
    private EmailUtils emailUtils;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        //01 调用service
        ResponseEntity<?> responseEntity = userService.login(loginRequest);
        if (responseEntity.getStatusCode() == HttpStatus.OK){
            //02 返回user给前端
            // 03 返回token 给前端
            User user = (User) responseEntity.getBody();
            ResponseEntity<?> entity = createTokenAndUserVo(user);
            return entity;
        }
        return responseEntity;
    }

    /**
     * 创建token 和 返回 userVo
     * @param user
     * @return
     */
    private ResponseEntity<?> createTokenAndUserVo(User user) {

        //1.生成Token
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId",user.getId());
        map.put("userName",user.getAccount());
        String token = jwtUtil.createToken(map);

        //2.存储到Redis中，保存1天 1h
        String key = RedisConst.USER_TOKEN_PREFIX +":"+ user.getId();

        stringRedisTemplate.opsForValue().set(
            key,  //key
            token,                         //值
            Duration.ofDays(7)            //过期时间
        );

        //3.封装UserVo对象
        //User 转成  userVo
        UserVo userVo = new UserVo();
//        userVo.setId(user.getId());
//        userVo.setAccount(user.getAccount());
//        userVo.setNickname(user.getNickname());
//        userVo.setRealName(user.getRealName());
//        userVo.setState(user.getState());

        BeanUtils.copyProperties(user,userVo);

        //4.以响应头的方式发送Token回去
        return ResponseEntity.ok()
                        .header("Authorization", token)
                        //告知浏览器可以访问 Authorization 响应头，否则访问不了
                        .header("Access-Control-Expose-Headers", "Authorization")
                        .body(userVo);

    }

    /**
     * 图片验证码
     */
    @GetMapping("/code")
    @ApiOperation("生成图片验证码")
    public ResponseEntity generateCaptcha() throws IOException {
        // awt
        //2) 创建缓存图片：指定宽
        int width = 110, height = 45;
        //参数3：图片的类型，使用RGB类型
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //3) 获取画笔对象
        Graphics graphics = image.getGraphics();
        //4) 设置画笔颜色，并且填充矩形区域
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        //创建一个StringBuider对象
        StringBuilder sb = new StringBuilder();
        //5) 从字符数组中随机得到字符
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        char[] arr = characters.toCharArray();

        Random random = new Random();

        for (int i = 0; i < 2; i++) {
            //随机从0到长度之间去取索引
            int index = random.nextInt(arr.length);
            //得到1个字符
            char c = arr[index];
            sb.append(c);
            //6) 设置字体，大小为22，设置字的颜色随机
            graphics.setFont(new Font(null, Font.BOLD + Font.ITALIC, 22));
            graphics.setColor(getColor());
            //7) 将每个字符画到图片，位置：5+(i*23), 25
            //将字符转成字符串
            graphics.drawString(String.valueOf(c), 5 + (i * 23), 25);
        }

        //8) 画干扰线8条线，线的位置是随机的，x范围在width之中，y的范围在height之中。
        for (int i = 0; i < 3; i++) {
            graphics.setColor(getColor());
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }
        //9) 将缓存的图片输出到响应输出流中
        //参数1：要输出的缓存图片，参数2：格式，参数3：输出到哪个流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);

        //随机生成一个key，前面加上code::
        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        //得到验证码，将生成的字符串放到Redis中，键随机生成，10分钟过期
        stringRedisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));

        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);

        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = "data:image/jpeg;base64," + encoder.encodeToString(outputStream.toByteArray());
        //返回的数据包含验证码的key和图片
        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //redis中键
        data.put("base64Img", base64Img);  //图片
        //返回key和图片的base64的编码
        return ResponseEntity.ok(data);
    }

    //写一个方法随机获取颜色
    private Color getColor() {
        Random random = new Random();
        //红，绿，蓝取值0-255，随机生成 0-255之间数
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }

    /**
     *  如何拿到token
     * @param token
     * @return
     *   获取token:
     *     1、 String token = request.getHeader("token");
     *     2、 @RequestHeader("token")String token
     *
     * @RequestHeader  获取请求头信息
     * @RequestParam   获取请求参数的信息
     *    注解中有属性
     *        value  数据的key
     *        defaultValue  可以设置默认值
     *        required      是否是必须的
     *     @RequestParam(value = "id",defaultValue = "1",required = false)
     *
     * @RequestPart    注解用于从多部分请求中提取某一部分的数据 (一般用于文件提取)
     */
    @GetMapping("/isLogged")
    public ResponseEntity<?> isLogged(@RequestHeader(value = "Authorization",
            required = false)String token){

        //判断是否过期
        boolean validate = jwtUtil.validateToken(token);

        //token 过期
        Assert.error(!validate, BusinessCode.TOKEN_NOT_EXISTS);

        //解析token
        String userId = jwtUtil.getValue(token, "userId");
        User user = userService.getById(userId);
        //把user 转成vo给前端
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user,userVo);

        return ResponseEntity.ok(userVo);
    };

    @GetMapping("/logout")
    public ResponseEntity<?> logout(@RequestHeader(value = "Authorization")String token){

        String userId = jwtUtil.getValue(token, "userId");
        String key = RedisConst.USER_TOKEN_PREFIX + ":" + userId;

        //清理
        stringRedisTemplate.delete(key);

        return ResponseEntity.ok().build();
    }

    @GetMapping("/sendSms/{telephone}")
    public ResponseEntity<?> sendSms(@PathVariable("telephone")String telephone){

        //1、获取验证码 hutool 中的工具随机
        String code = RandomUtil.randomNumbers(4);

        log.info("验证码是:{}",code);
//        smsUtil.sendSms(telephone, code);

        //在redis 发送短信进行记录
        //2、存储到redis中  过期时间是 5分钟
        String key = RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone;
        stringRedisTemplate.opsForValue().set(
                key,
                code,
                5,
                TimeUnit.MINUTES
        );

        //3、返回结果
        //18566230139
        //185****139
        String s1 = telephone.substring(0, 3);
        String s2 = "****";
        String s3 = telephone.substring(7);
        String phoneStr = s1 + s2 + s3;
        String msg = "用户"+phoneStr+"手机号码短信发送成功";

        return ResponseEntity.ok(msg);
    }

    /**
     * 发短信登录或注册
     */
    @PostMapping("/smsLogin")
    @ApiOperation("发短信登录或注册")
    public ResponseEntity<?> smsLogin(@RequestBody LoginRequest params, HttpServletRequest request) {

        // 校验验证码
        String code = params.getCode();
        String phone = params.getTelephone();
        //获取手机号
        //从Redis中获取验证码
        String codeKey = RedisConst.SHORT_MESSAGE_SERVICE + ":" + phone;;
        String redisCode = stringRedisTemplate.opsForValue().get(codeKey);
        //判断验证码是否过期
        Assert.error(StringUtils.isEmpty(redisCode), BusinessCode.INVALID_VERIFICATION);
        //判断验证码是否正确
        Assert.error(!redisCode.equals(code),BusinessCode.INVALID_VERIFICATION);
        //删除验证码
        stringRedisTemplate.delete(codeKey);

        //根据手机号查询用户信息  判断用户 是否以注册 如果注册 返回、没有注册进行登录
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
        wrapper.eq(User::getMobile,phone);
        User user = userService.getOne(wrapper);
        //如果用户不存在，创建新用户
        if (Objects.isNull(user)){
            //创建用户 进行注册
            user = new User();
            user.setAccount(phone);
            user.setMobile(phone);
            //密码进行加密
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            user.setPassword(encoder.encode("123"));

            userService.save(user);
        }
        return createTokenAndUserVo(user);
    }

    @GetMapping("/sendEmail")
    @ApiOperation("发送邮件")
    public ResponseEntity<String> sendEmail(String address) {

        //判断 这个邮箱是否 已经注册  返回信息提示
        Assert.error(!userService.isEmailExist(address),BusinessCode.EMAIL_EXIST_ERROR);

        //生成验证码
        String code = RandomUtil.randomNumbers(4);

        //创建一个线程
        /**
         *   1、Thread
         *   2、实现Runnable 接口
         *   3、Callable    接口
         *   4、线程池
         *   5、CompletableFuture 接口
         */
          new Thread(()->{
              //发送邮件
//              emailUtils.sendEmail(address,code);
          }).start();

        //验证存储在redis中
        String key = RedisConst.EMAIL_ADDRESS_PREFIX + ":" + address;
        stringRedisTemplate.opsForValue().set(
                key,
                code,
                5,
                TimeUnit.MINUTES);
        return ResponseEntity.ok("邮件发送成功...");
    }

    /**
     * 用户注册功能
     */
    @PostMapping("/register")
    @ApiOperation("用户注册")
    public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
        //从Redis中获取验证码是否正确
        String key = RedisConst.EMAIL_ADDRESS_PREFIX + ":" + registerRequest.getEmail();
        //判断验证码是否过期 (邮箱中的验证码)
        String redisCode = stringRedisTemplate.opsForValue().get(key);
        Assert.error(StringUtils.isEmpty(redisCode), BusinessCode.INVALID_VERIFICATION);

        //判断验证码是否正确 (邮箱中的验证码)
        Assert.error(!redisCode.equals(registerRequest.getValidCode()), BusinessCode.VERIFICATION_ERROR);

        //判断用户名是否重复
        Assert.error(!userService.isAccountExist(registerRequest.getAccount()),BusinessCode.USER_IS_EXIST);

        //判断手机是否重复
        Assert.error(!userService.isMobileExist(registerRequest.getMobile()),BusinessCode.MOBILE_IS_EXIST);
        //创建用户对象
        User user = new User();
        //将注册信息复制到user对象中
        BeanUtils.copyProperties(registerRequest,user);
        //秘密需要加密
        user.setPassword(new BCryptPasswordEncoder().encode(registerRequest.getPassword()));

        //调用业务层注册
        userService.save(user);

        //默认已经登录
        return createTokenAndUserVo(user);
    }
}
